#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
-------------------------------------------------
   File Name：    case_manager.py
   Remark :       用例管理模块：解析 YAML 用例、校验数据、缓存至 Redis
-------------------------------------------------
"""
import hashlib
import json
import os
from pathlib import Path
from typing import Dict, Any

import pytest
import yaml
from pydantic import ValidationError

from apitesting.config import paths
from apitesting.config import settings
# from apitesting.models.test_suite.common_info import MarkModel
from apitesting.src.models import MarkModel
from apitesting.src.base import errors
from apitesting.src.base.pydantic_parser import parse_error
from apitesting.common.util.logger import log as Log, log
from apitesting.common.file.file_handler import File
from apitesting.common.db.redis_handler import redis_handler
from apitesting.src.models.test_suite.test_suite_model import RedisCaseData

# from apitesting.models.test_suite import MarkModel

__all__ = [
    "validate_case_data_from_redis",
    "get_testcase_data",  # 获取用例数据
    "get_testcase_mark",  # 获取用例的标记
    "CaseManager"  # redis缓存测试用例数据和获取用例
]


def validate_case_data_from_redis(pydantic_verify: bool) -> None:
    """
    校验用例数据。

    :param pydantic_verify: 是否进行 Pydantic 校验
    """
    if pydantic_verify:
        # 获取所有用例数据
        test_case_list: list = redis_handler.get_prefix(f'{redis_handler.case_data_prefix}:')
        error_count: int = 0

        # 遍历所有用例数据进行校验
        for test_case in test_case_list:
            try:
                # 使用 Pydantic 模型校验用例数据
                RedisCaseData.model_json_schema(json.loads(test_case))
            except json.JSONDecodeError as e:
                # 记录 JSON 解析错误
                Log.error(f'JSON 解析错误: {e}')
            except ValidationError as e:
                # 统计校验错误数量
                error_count += parse_error(e)

        # 如果存在校验错误，则抛出异常
        if error_count > 0:
            Log.error(f'测试用例数据校验失败，共有 {error_count} 处错误, 错误详情请查看日志')
            raise ValueError(f'测试用例数据校验失败，共有 {error_count} 处错误, 错误详情请查看日志')


def get_testcase_data(filename: str) -> list:
    """
    获取用例数据

    :param filename: 测试用例数据文件名称
    :return: 返回一个元组，包含测试用例数据列表和对应的ID列表
    """

    # 从Redis中获取测试用例数据
    case_data: dict = json.loads(redis_handler.get(f'{redis_handler.case_data_prefix}:{filename}'))

    # 定义错误信息，用于提示测试用例文件中缺少必要的信息
    test_steps_error = f'请求测试用例数据文件 {filename} 缺少 test_steps 信息, 请检查测试用例文件内容'

    try:
        # 数据验证
        case_cache_data = RedisCaseData(**case_data)
    except ValidationError as e:
        Log.error(f'测试用例数据文件 {filename} 解析错误: {e}')
        raise e

    # 获取测试用例配置信息
    # config = case_data.get('config')
    config = case_cache_data.config.model_dump()

    # 获取测试步骤信息, 本来校验了的，为避免后续修改逻辑，这里还是保留
    _steps = case_cache_data.test_steps
    if _steps is None:
        raise errors.RequestDataParseError(test_steps_error)

    # 如果测试步骤是一个列表，遍历每个步骤，处理每个测试用例数据项
    if isinstance(_steps, list):
        _ddt_data_list = []
        marked_ddt_data_list = []

        # 1、遍历每个测试用例数据项
        for case in _steps:
            # 如果测试用例数据项是一个字典，字典是正常数据，非正常直接报错即可
            if isinstance(case.model_dump(), dict):
                # 将多个用例步骤列表添加到列表中
                _case_data = {'config': config, 'test_steps': case.model_dump()}
                # 获取步骤列表，方便后面拿步骤id
                _ddt_data_list.append(_case_data)
                mark = get_testcase_mark(_case_data)

                # 如果有mark标记，添加pytest.param对象，没有则添加普通数据
                if mark is not None:
                    # 添加的pytest.param对象
                    marked_ddt_data_list.append(
                        pytest.param(
                            _case_data,
                            marks=[getattr(pytest.mark, m) for m in mark]
                        )
                    )
                else:
                    # 没有就不加标记，普通数据
                    marked_ddt_data_list.append(_case_data)
            else:
                # 不是字典抛出错误
                raise errors.RequestDataParseError(test_steps_error)

        # 2、获取id列表
        # ids = get_ids(_ddt_data_list)
        return marked_ddt_data_list  # , ids

    # 如果测试步骤数据类型不是列表，抛出异常
    else:
        # 如果测试步骤数据类型不正确，抛出异常
        raise errors.RequestDataParseError(f'请求测试用例数据文件 {filename} 格式错误, 请检查用例数据文件内容')


def get_testcase_mark(case_data: dict) -> list[str] | None:
    """
    获取测试用例的mark标记。

    该函数首先尝试从测试用例数据的局部测试步骤中获取mark标记，
    如果未找到，则尝试从全局配置中获取mark标记。
    如果在局部测试步骤和全局配置中都找不到mark标记，则返回None。

    参数:
    case_data (dict): 包含测试用例数据的字典。

    返回:
    list[str] | None: 返回mark标记的列表，如果没有找到mark标记则返回None。
    """
    try:
        # 获取局部mark标记
        mark = case_data['test_steps']['mark']
    except (KeyError, TypeError):
        try:
            # 获取全局mark标记
            mark = case_data['config']['mark']
        except (KeyError, TypeError):
            # 如果没有找到mark标记，返回None
            mark = None

    # 数据检验，是否符合mark模型 list[str] | None, 符合直接返回
    return MarkModel(mark=mark).mark


class CaseManager:
    """
    用例数据解析器，用于解析用例数据，并进行相关校验。
    """

    def __init__(self):
        """初始化 Redis 连接"""
        redis_handler.check_connection()
        self.redis_client = redis_handler

    @staticmethod
    def _calculate_file_hash(file_path: str) -> str:
        """
        计算文件的哈希值，用于判断文件是否有更新
        """
        hasher = hashlib.md5()
        with open(file_path, 'rb') as f:
            buf = f.read()
            hasher.update(buf)
            return hasher.hexdigest()

    def load_data_to_redis_cache(self, file_path: str) -> None:
        """
        将 YAML 用例解析并存储到 Redis。

        Args:
            file_path (str): 要解析的 YAML 文件绝对路径
        """
        # 获取文件名
        file_name = Path(file_path).name
        file_type = Path(file_path).suffix[1:]

        # Redis key 格式
        redis_key = f"test_cases:{settings.project.name}:{file_name}"

        # 计算文件哈希值
        file_hash = self._calculate_file_hash(file_path)

        # 检查 Redis 中是否已有该文件的哈希值
        existing_data = self.redis_client.get_key(redis_key)
        if existing_data:
            existing_data = json.loads(existing_data)
            if existing_data["hash"] == file_hash:
                print(f"No changes detected for {file_path}. Using cached data.")
                return

        # 文件有更新或首次加载，重新读取 YAML 文件
        test_data_json = File.read_file_from_type(file_path=file_path, file_type=file_type)

        # 2. 校验用例
        try:
            validate_case_data_from_redis(pydantic_verify=True)
            log.info("✅ 用例校验完成")
        except Exception as e:
            log.error(f"❌ 用例校验失败: {e}")
            raise Exception("框架初始化失败") from e

        # 将数据存储为 JSON 格式
        redis_value = {
            "hash": file_hash,
            "data": test_data_json
        }

        # 将新数据存储到 Redis
        self.redis_client.set(
            redis_key,
            json.dumps(redis_value, indent=4, ensure_ascii=False)
        )
        print(f"Updated cache for {file_path} in Redis.")

    def get_test_case_from_redis(self, file_name: str) -> Dict[str, Any]:
        """
        从 Redis 中获取缓存的用例数据并解析为字典
        """
        # Redis key
        redis_key = f"test_cases:{settings.project.name}:{file_name}"

        # 从 Redis 中获取缓存数据
        cached_data = self.redis_client.get(redis_key)
        if not cached_data:
            raise ValueError(f"No data found in Redis for key: {redis_key}")
        else:
            # 将 JSON 转为字典
            test_case_data: dict = json.loads(cached_data)
            return test_case_data

    @staticmethod
    def clean_redis_cache(clean_cache: bool = True) -> None:
        """清理 Redis 缓存"""
        # 检查是否需要清理缓存
        if clean_cache:
            # 记录清理缓存的开始
            Log.info(
                f"🚀 开始清理前缀为'{redis_handler.project_name}'且不包括前缀“{redis_handler.token_prefix}”的缓存测试数据")

            try:
                # 尝试清理缓存
                redis_handler.delete_prefix(
                    # 删除所有前缀为redis_client.prefix的key
                    prefix=redis_handler.project_name,
                    # 排除token前缀
                    exclude=redis_handler.token_prefix
                )

                # 记录清理缓存的完成
                Log.info("✅ 缓存test_data已成功清理")
            except Exception as e:
                # 记录异常信息
                Log.error("Failed to clean cache data: %s", str(e))
        else:
            Log.info("Skipping cache cleanup")

    def load_cases(self) -> None:
        """
        初始化用例数据
        """
        for root, _, files in os.walk(os.path.join(paths.TEST_DATA_DIR.as_posix(), settings.project.name)):
            for file in files:
                # print(f"file_path: {file_path}")
                file_path = os.path.join(root, file)
                self.load_data_to_redis_cache(file_path)
        # 获取所有的用例文件路径
        # all_case_data_files: list[str] = File.get_all_testdata_filepaths()
        # Log.info(f"🚀 ******开始初始化用例数据{len(all_case_data_files)}条。")
        #
        # for case_data_file in all_case_data_files:
        #     self.load_data_to_redis_cache(case_data_file)
        #
        # # 结果通知
        # Log.info('✅ ****** 用例数据初始化完成')


CaseDataHandler = CaseManager()

if __name__ == '__main__':
    # get_testcase_data(filename="生成验证码.yaml")
    CaseManager().load_cases()
    # print(RedisTestCaseCache().get_test_case_from_redis(file_name="生成验证码.yaml"))
    # case_data_init(pydantic_verify=True)
    # case_id_unique_verify()
